package com.ucode.springboot.starter.security.service;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;

import com.ucode.springboot.starter.security.entity.UcodeAuthenticatioToken;
import com.ucode.springboot.starter.security.entity.UcodeGrantedAuthority;
import com.ucode.springboot.starter.security.entity.UcodeUser;
import com.ucode.springboot.starter.security.entity.UcodeUserDetails;
import com.ucode.tool.util.JwtTokenUtils;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import io.jsonwebtoken.Claims;

/**
 * Security相关操作类
 * @author: liliang
 * @date: 2019年12月20日 下午3:50:51
 */
@Component
public class UcodeSecurityOperationService {
    
    private static Log log = LogFactory.get(UcodeSecurityOperationService.class);
    /**
     * 权限列表
     */
    private static final String AUTHORITIES = "authorities";
    
    /**
     * 账号是否未过期
     */
    private static final String ACCOUNTNONEXPIRED = "accountNonExpired";
    /**
     * 账号是否未锁定
     */
    private static final String ACCOUNTNONLOCKED = "accountNonLocked";
    /**
     * 密码是否未过期
     */
    private static final String CREDENTIALSNONEXPIRED = "credentialsNonExpired";
    /**
     * Jwt秘钥
     */
    @Value("${ucode.security.jwt.secret}")
    private String secret;
    /**
     * Jwt token过期时间,单位秒
     */
    @Value("${ucode.security.jwt.expiration}")
    private Long expiration;
    /**
     * Get请求资源，忽略授权(配置网站静态资源等),多个资源之间用逗号分割
     */
    @Value("${ucode.security.ignoreGetUrls}")
    private String[] ignoreGetUrls;
    /**
     * 匿名访问地址(登录、注册等请求),多个资源之间用逗号分割
     */
    @Value("${ucode.security.anonymousUrls}")
    private String[] anonymousUrls; 
    
    //token校验是否本地校验
    private boolean isLocalCheck = false;
    
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Autowired
    private UserDetailsService userDetailsService;
    
    
    /**
     * 系统登录认证
     * @param request
     * @param username
     * @param password
     * @return
     */
    public UcodeAuthenticatioToken login(HttpServletRequest request, String username, String password) {
        UcodeAuthenticatioToken token = new UcodeAuthenticatioToken(username, password);
        token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        
        long startTime = DateUtil.current(false);
        log.debug("执行登录认证的开始时间:{}",startTime);
        // 执行登录认证过程
        Authentication authentication = authenticationManager.authenticate(token);
        long endTime = DateUtil.current(false);
        log.debug("执行登录认证的结束时间:{},共耗时:{}",endTime,endTime-startTime);
        // 认证成功存储认证信息到上下文
        SecurityContextHolder.getContext().setAuthentication(authentication);
        
        // 生成令牌并返回给客户端
        return new UcodeAuthenticatioToken(authentication.getPrincipal(),null,generateToken(authentication));
    }

    
    /**
     * 获取令牌进行认证
     * @author liliang
     * @date 2019年12月20日
     * @param request
     * @desc
     */
    public void checkAuthentication(HttpServletRequest request) {
        // 获取令牌并根据令牌获取登录认证信息
        Authentication authentication = null;
        String token = getToken(request);
        log.debug("当前登录用户token:"+token);
        if(StrUtil.isNotBlank(token)){
            authentication = getCurrentAuthentication();
            if( authentication == null){
                
                Claims claims = JwtTokenUtils.getClaimsFromToken(token,secret);
                String username = null;
                String userId = null;
                if(claims == null || JwtTokenUtils.isTokenExpired(token,secret) 
                        || StrUtil.isBlank(username = claims.getSubject())
                        || StrUtil.isBlank(userId = claims.getId())){
                    log.debug("JWT令牌已过期");
                    return;
                }
                
                /**
                 * 验证token
                 * 1、只检验token,不查库,就把权限放进jwt中即可
                 * 2、检验token,需要查库，实际应用中,需要查库，因为用户的状态以及权限随时可能变更
                 */
                List<UcodeGrantedAuthority> authorities = null;
                UserDetails userDetails = null;
                if(isLocalCheck){
                    //第一种:不查库,本地校验
                    Object authors = claims.get(AUTHORITIES);
                    authorities = new ArrayList<UcodeGrantedAuthority>();
                    if (authors != null && authors instanceof List) {
                      for (Object object : (List) authors) {
                          authorities.add(new UcodeGrantedAuthority((String) ((Map) object).get("authority")));
                      }
                    }
                    UcodeUser ucodeUser = new UcodeUser();
                    ucodeUser.setUsername(username);
                    ucodeUser.setUserId(userId);
                    ucodeUser.setAccountNonExpired((Boolean)claims.get(ACCOUNTNONEXPIRED));
                    ucodeUser.setAccountNonLocked((Boolean)claims.get(ACCOUNTNONLOCKED));
                    ucodeUser.setCredentialsNonExpired((Boolean)claims.get(CREDENTIALSNONEXPIRED));
                    ucodeUser.setAuthoritys(authorities);
                    userDetails = new UcodeUserDetails(ucodeUser);
                }else{
                   //第二种:查库
                    userDetails = userDetailsService.loadUserByUsername(username);
                    if(userDetails == null){
                        log.debug("JWT令牌未查询到目标用户");
                        return ;
                    }
                    authorities = (List<UcodeGrantedAuthority>) userDetails.getAuthorities();
                }
                
              //这里可以只告诉springsecurity用户的权限即可,账户密码不用提供;但以便以后的操作中可以获取当前登录用户，这里把username传给springsecurity
               authentication = new UcodeAuthenticatioToken(userDetails, null, authorities, token);
               
               ((UcodeAuthenticatioToken) authentication).setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                
            }else if(!JwtTokenUtils.validateToken(token, getCurrentUsername(),secret)){
                // 如果上下文中Authentication非空，且请求令牌合法，直接返回当前登录认证信息,反之返回null
                authentication = null;
            }
        }
        
        // 设置登录认证信息到上下文
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }
    
    /**
     * 生成令牌
     * @author liliang
     * @date 2019年12月21日
     * @param authentication
     * @return
     */
    public String generateToken(Authentication authentication){
        Map<String, Object> claims = new HashMap<>(1);
        claims.put(AUTHORITIES, authentication.getAuthorities());
        UcodeUserDetails userDetails = getUserDetails(authentication);
        String token = null;
        UcodeUser user = null;
        if(userDetails != null && (user = userDetails.getUser()) != null){
            
            claims.put(ACCOUNTNONEXPIRED, user.isAccountNonExpired());
            claims.put(ACCOUNTNONLOCKED, user.isAccountNonLocked());
            claims.put(CREDENTIALSNONEXPIRED, user.isCredentialsNonExpired());
            
            token = JwtTokenUtils.generateToken(user.getUserId().toString(),user.getUsername(),claims,expiration,secret);
        }
        return token;
    }
    
    /**
     * 获取用户名
     * @author liliang
     * @date 2019年12月21日
     * @param authentication
     * @return
     */
    public String getUsername(Authentication authentication) {
        String username = null;
        if(authentication != null) {
            Object principal = authentication.getPrincipal();
            if(principal != null && principal instanceof UcodeUserDetails) {
                username = ((UcodeUserDetails) principal).getUsername();
            }
        }
        return username;
    }
    
    /**
     * 获取用户信息
     * @author liliang
     * @date 2019年12月21日
     * @param authentication
     * @return
     */
    public UcodeUserDetails getUserDetails(Authentication authentication) {
        UcodeUserDetails userDetails = null;
        if(authentication != null) {
            Object principal = authentication.getPrincipal();
            if(principal != null && principal instanceof UcodeUserDetails) {
                userDetails = ((UcodeUserDetails) principal);
            }
        }
        return userDetails;
    }
    
    /**
     * 当前登陆用户名
     * @author liliang
     * @date 2019年12月27日
     * @return
     */
    public String getCurrentUsername(){
        UcodeUserDetails user = getCurrentUser();
        if(user != null){
            return user.getUsername();
        }
        return null;
    }
    
    /**
     * 当前登录用户信息
     * @author liliang
     * @date 2019年12月27日
     * @return
     */
    public UcodeUserDetails getCurrentUser() {
        UcodeUserDetails user = null;
        Authentication authentication = getCurrentAuthentication();
        if(authentication != null) {
            Object principal = authentication.getPrincipal();
            if(principal != null && principal instanceof UcodeUserDetails) {
                user = ((UcodeUserDetails) principal);
            }
        }
        return user;
    }
    
    
    /**
     * 获取当前登录信息
     * @author liliang
     * @date 2019年12月20日
     * @return
     */
    public Authentication getCurrentAuthentication() {
        if(SecurityContextHolder.getContext() == null) {
            return null;
        }
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return authentication;
    }
    
      /**
      * 获取请求token
      * @param request
      * @return
      */
     public String getToken(HttpServletRequest request) {
         Enumeration<String> names = request.getParameterNames();
         StringBuffer sb = new StringBuffer();
         while(names.hasMoreElements()){
             String key = names.nextElement();
             String value =  request.getParameter(key);
             sb.append(key);
             sb.append(":");
             sb.append("[");
             sb.append(value);
             sb.append("]");
             sb.append("*-*");
         }
         log.info("请求参数："+sb.toString());
         
         String token = request.getHeader(JwtTokenUtils.TOKEN_HEADER);
         if(StrUtil.isBlank(token)){
             token = request.getParameter(JwtTokenUtils.TOKEN_HEADER);
         }
         String tokenHead = "Bearer ";
         if(StrUtil.isNotBlank(token)){
             if(token.contains(tokenHead)){
                 token = token.substring(tokenHead.length());
             }
         }else
             token = null;
         return token;
     }

    public String getSecret() {
        return secret;
    }
    public Long getExpiration() {
        return expiration;
    }
    public String[] getIgnoreGetUrls() {
        return ignoreGetUrls;
    }
    public String[] getAnonymousUrls() {
        return anonymousUrls;
    }
    
}
